package com.xingchi.tornado.mybatisplus.plugins;

import com.xingchi.tornado.constant.DateTimeFormat;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Proxy;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;

/**
 * SQL日志拦截器
 * 拦截MyBatis执行的SQL语句，并记录到日志中
 *
 * @author xiaoya
 * @date 2023/6/12 17:35
 * @modified xiaoya
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class})
})
public class SqlLogInterceptor implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(SqlLogInterceptor.class);

    private static final String PROXY_MAPPED_STATEMENT_ATTR = "h.target.delegate.mappedStatement";
    private static final String PROXY_CONFIGURATION_ATTR = "h.target.delegate.configuration";
    private static final String CONFIGURATION_ATTR = "delegate.configuration";

    /**
     * SQL日志执行的最大长度，超过这个长度将被截断
     */
    private static final int MAX_SQL_LENGTH = 5000;

    /**
     * 敏感字段匹配模式，用于防止敏感信息泄露
     */
    private static final Pattern SENSITIVE_PATTERN = Pattern.compile(
            "(password|passwd|pwd|secret|token|credentials|key)",
            Pattern.CASE_INSENSITIVE
    );

    /**
     * 用于异步处理SQL日志的线程池
     */
    private final ExecutorService executorService;

    /**
     * 是否启用异步处理
     */
    private boolean asyncEnabled = true;

    /**
     * 构造函数，初始化异步处理线程池
     */
    public SqlLogInterceptor() {
        this.executorService = new ThreadPoolExecutor(
                5, 10,
                60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(6400),
                new ThreadFactory() {
                    private final AtomicInteger threadNumber = new AtomicInteger(1);
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t = new Thread(r, "sql-log-thread-" + threadNumber.getAndIncrement());
                        t.setDaemon(true);
                        return t;
                    }
                },
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );
    }

    /**
     * 拦截方法，记录SQL执行情况
     *
     * @param invocation 调用信息
     * @return 执行结果
     * @throws Throwable 可能的异常
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();

        Object arg = invocation.getArgs()[0];
        MetaObject metaObject = MetaObject.forObject(statementHandler,
                SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                new DefaultReflectorFactory());

        Object result;
        long start = System.currentTimeMillis();
        try {
            result = invocation.proceed();
        } finally {
            // 获取并处理SQL
            long end = System.currentTimeMillis();
            long executionTime = end - start;

            if (asyncEnabled) {
                // 异步处理SQL日志
                executorService.execute(() -> processSqlLog(metaObject, boundSql, statementHandler, executionTime));
            } else {
                // 同步处理SQL日志
                processSqlLog(metaObject, boundSql, statementHandler, executionTime);
            }
        }
        return result;
    }

    /**
     * 处理SQL日志
     *
     * @param metaObject 元数据对象
     * @param boundSql SQL绑定对象
     * @param statementHandler Statement处理器
     * @param executionTime 执行时间
     */
    private void processSqlLog(MetaObject metaObject, BoundSql boundSql,
                               StatementHandler statementHandler, long executionTime) {
        try {
            if (!log.isDebugEnabled()) {
                return;
            }

            String sql;
            if (Proxy.isProxyClass(statementHandler.getClass())) {
                MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(PROXY_MAPPED_STATEMENT_ATTR);
                Configuration configuration = (Configuration) metaObject.getValue(PROXY_CONFIGURATION_ATTR);
                sql = this.getSql(boundSql, configuration);
            } else {
                Configuration configuration = (Configuration) metaObject.getValue(CONFIGURATION_ATTR);
                sql = this.getSql(boundSql, configuration);
            }

            // 截断过长的SQL
            if (sql.length() > MAX_SQL_LENGTH) {
                sql = sql.substring(0, MAX_SQL_LENGTH) + "... [TRUNCATED]";
            }

            log.debug("SQL执行语句: {}", sql);
            log.debug("SQL执行时间: {}ms", executionTime);
        } catch (Exception e) {
            log.warn("处理SQL日志时发生异常", e);
        }
    }

    /**
     * 获取执行的SQL语句
     *
     * @param boundSql SQL绑定对象
     * @param configuration MyBatis配置
     * @return 填充了参数的SQL语句
     * @throws SQLException SQL异常
     */
    private String getSql(BoundSql boundSql, Configuration configuration) throws SQLException {
        // 获取预编译后的SQL
        String sql = boundSql.getSql();
        if (StringUtils.isBlank(sql)) {
            throw new SQLException("运行sql不能为空");
        }
        sql = this.cleanSql(sql);

        // 获取参数传递过来的值
        Object parameterObject = boundSql.getParameterObject();
        if (parameterObject == null) {
            return sql;
        }

        // 获取参数元数据信息
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        if (CollectionUtils.isEmpty(parameterMappings)) {
            return sql;
        }

        try {
            // 获取参数注册器
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = this.fillParameter(sql, parameterObject);
            } else {
                // 如果是对象，对象值等相关属性
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    // 获取属性名
                    String property = parameterMapping.getProperty();

                    // 跳过敏感信息的参数填充
                    if (SENSITIVE_PATTERN.matcher(property).find()) {
                        sql = fillParameter(sql, "******");
                        continue;
                    }

                    if (metaObject.hasGetter(property)) {
                        Object value = metaObject.getValue(property);
                        sql = fillParameter(sql, value);
                    } else if (boundSql.hasAdditionalParameter(property)){
                        Object value = boundSql.getAdditionalParameter(property);
                        sql = fillParameter(sql, value);
                    }
                }
            }
        } catch (Exception e) {
            log.warn("解析SQL语句时发生异常", e);
        }

        return sql;
    }

    /**
     * 填充SQL参数
     *
     * @param sql SQL语句
     * @param parameterObject 参数对象
     * @return 填充了参数的SQL语句
     */
    private String fillParameter(String sql, Object parameterObject) {
        String handler;

        if (parameterObject == null) {
            handler = "NULL";
        } else if (parameterObject instanceof String) {
            handler = "'" + escapeString(parameterObject.toString()) + "'";
        } else if (parameterObject instanceof Date) {
            handler = "'" + new SimpleDateFormat(DateTimeFormat.DATE_TIME_FORMAT).format(parameterObject) + "'";
        } else if (parameterObject instanceof LocalDateTime) {
            handler = "'" + DateTimeFormatter.ofPattern(DateTimeFormat.DATE_TIME_FORMAT)
                    .format((TemporalAccessor) parameterObject) + "'";
        } else if (parameterObject instanceof LocalDate) {
            handler = "'" + DateTimeFormatter.ofPattern(DateTimeFormat.DATE_FORMAT)
                    .format((TemporalAccessor) parameterObject) + "'";
        } else if (parameterObject instanceof LocalTime) {
            handler = "'" + DateTimeFormatter.ofPattern(DateTimeFormat.TIME_FORMAT)
                    .format((TemporalAccessor) parameterObject) + "'";
        } else {
            handler = parameterObject.toString();
        }

        return sql.replaceFirst("\\?", handler);
    }

    /**
     * 转义字符串，防止SQL注入
     *
     * @param input 输入字符串
     * @return 转义后的字符串
     */
    private String escapeString(String input) {
        if (input == null) {
            return "";
        }
        return input.replace("'", "''")
                .replace("\\", "\\\\")
                .replace("%", "\\%")
                .replace("_", "\\_");
    }

    /**
     * 清理SQL语句，去除注释和格式化
     *
     * @param sql 原始SQL
     * @return 清理后的SQL
     */
    public String cleanSql(String sql) {
        if (sql == null) {
            return "";
        }

        // 去除 SQL 注释
        sql = sql.replaceAll("/\\*.*?\\*/", "");

        // 去除行注释
        sql = sql.replaceAll("--[^\n]*", "");

        // 去除多余的空格和换行符
        sql = sql.replaceAll("[\\s]+", " ");

        // 去除语句开头和结尾的空格
        sql = sql.trim();

        return sql;
    }

    /**
     * 设置拦截器属性
     *
     * @param properties 属性
     */
    @Override
    public void setProperties(Properties properties) {
        String asyncEnabledStr = properties.getProperty("asyncEnabled");
        if (asyncEnabledStr != null) {
            this.asyncEnabled = Boolean.parseBoolean(asyncEnabledStr);
        }
    }

    /**
     * 关闭拦截器时释放资源
     */
    public void shutdown() {
        if (executorService != null && !executorService.isShutdown()) {
            executorService.shutdown();
            try {
                if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
                    executorService.shutdownNow();
                }
            } catch (InterruptedException e) {
                executorService.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
    }
}
